#!/usr/bin/env python
# encoding: utf-8
"""
TwoPartyLegislature.py

Created by Drew Conway <agc282@nyu.edu> on 2008-10-07.
Copyright (c) 2008. All rights reserved, see GNU LGPL.

PyVote is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

"""
__version__="0.03"

import sys
import os
import unittest
import warnings
from scipy import stats,random,array,median,mean,std,histogram,zeros
from math import sqrt,pi,e
import pylab as P

class TwoPartyLegislature:
# Object where agents are created and exist.  Agents can fit a rough random bi-modal distribution when
# ditribution is set to 'None', or a distribution can be provided by the user.  This distribution must
# be a list containing 10 ints that sum to 'num_agents'.  This construction is used to fix the distribution 
# of ideologies within the legislautre.
    def __init__(self,num_agents,given_ideologies=None,distribution=None,tol=None):
        # Only the number of agents and a possible ideological distribution are provided outside the class
        
        # Class variables provided inside the class
        self.agents=[]  # Agents in legislature
        self.bills=[]   # Bills voted on by legislature
        self.num_liberals=0 
        self.num_conservatives=0
        self.ideologies=[]  # List of agent ideologies
        self.tolerances=[]  # List of adjusted agent tolerances
        self.dist=None

        if given_ideologies is None:
            if distribution is None:
            # When no ideological distribution is provided, fill out legislature with agents and do
            # appropriate record keeping.
                for a in range(0,num_agents):
                    A=TwoPartyAgent(tolerance_function=tol)
                    A.set_id(a)
                    if A.get_party()<1:
                        self.num_liberals=self.num_liberals+1
                        self.ideologies.append(A.get_ideology())
                        self.tolerances.append(A.get_tolerance())
                        self.agents.append(A)
                    else:
                        self.num_conservatives=self.num_conservatives+1
                        self.ideologies.append(A.get_ideology())
                        self.tolerances.append(A.get_tolerance())
                        self.agents.append(A)
                self.dist=histogram(self.ideologies,10)[0]
            else:
            # If distribution is provided, first check to make sure the breakdown equals the total number of agents
            # inside the legislature
                if sum(distribution) != num_agents:
                    raise IndexError('Distribution count must equal the number of agents')
                else:
                    self.dist=distribution
                    agent_count=0
                    for i in range(0,len(distribution)):
                        agents_in_bin_count=0
                        while agents_in_bin_count<distribution[i]:
                        # Next, create agents that fit the given ideolgical distribution, and perform appropraite record keeping
                            A=TwoPartyAgent(tolerance_function=tol)
                            if (A.ideology-(float(i)/len(distribution)))>0.0 and (A.ideology-(float(i)/len(distribution)))<0.1:
                            # this test makes sure the agent created fits the ideological bin being filled
                                if A.get_party()<1:
                                    self.num_liberals=self.num_liberals+1
                                    self.ideologies.append(A.get_ideology())
                                    self.tolerances.append(A.get_tolerance())
                                    A.set_id(agent_count)
                                    self.agents.append(A)
                                    agents_in_bin_count=agents_in_bin_count+1
                                    agent_count=agent_count+1
                                else:
                                    self.num_conservatives=self.num_conservatives+1
                                    self.ideologies.append(A.get_ideology())
                                    self.tolerances.append(A.get_tolerance())
                                    A.set_id(agent_count)
                                    self.agents.append(A)
                                    agents_in_bin_count=agents_in_bin_count+1
                                    agent_count=agent_count+1
        else:
            if len(given_ideologies)!=num_agents:
                warnings.warn('Number of agents does not match the number of ideologies given, using ideologies and ignoring number of agents', UserWarning)
            for i in range(0,len(given_ideologies)):
                A=TwoPartyAgent(ideology_input=given_ideologies[i],tolerance_function=tol)
                A.set_id(i)
                if A.get_party()<1:
                    self.num_liberals=self.num_liberals+1
                    self.ideologies.append(A.get_ideology())
                    self.tolerances.append(A.get_tolerance())
                    self.agents.append(A)
                else:
                    self.num_conservatives=self.num_conservatives+1
                    self.ideologies.append(A.get_ideology())
                    self.tolerances.append(A.get_tolerance())
                    self.agents.append(A)
            self.dist=histogram(self.ideologies,10)[0]
            
                    
        
        
    def size(self):
    # return legislature size
        return len(self.agents)
        
    def legislators(self):
    # return a list of agents in legislature
        return self.agents
    
    def get_ideologies(self):
    # Return list of all agent's ideologies
        return self.ideologies
    
    def get_utilities(self):
    # Return utilities for all agents
        utilities=[]
        for a in self.legislators():
            utilities.append(a.get_utility())
        return utilities

    def get_distribution(self):
        return self.dist
    
    def get_num_liberals(self):
        return self.num_liberals
        
    def get_num_conservatives(self):
        return self.num_conservatives
        
    def get_ideologies(self):
    # Return list of legislature ideologies
        return self.ideologies
        
    def majority(self):
    # Returns 0 if liberal majority and 1 if conservative
        if self.get_num_liberals()>self.get_num_conservatives():
            return 0
        else:
            return 1
        
    def add_bill(self, bill):
        self.bills.append(bill)
        
    def get_all_bills(self):
        return self.bills
        
    def get_passed_bills(self):
        passed=[]
        for b in self.bills:
            if b.bill_pass:
                passed.append(b)
        return passed
        
    def get_agent(self, agent_id):
    # return a specific agent from legislature
        return self.agents[agent_id]
        
    def find_liberals(self):
    # return a list of all liberal agents in legislature
        liberals_index=[]
        for i in range(0,len(self.agents)):
            if self.agents[i].get_party()<1:
                liberals_index.append(i)
        return liberals_index
        
    def find_conservatives(self):
    # return a list of all conservative agents in legislature
        conservatives_index=[]
        for i in range(0,len(self.agents)):
            if self.agents[i].get_party()>0:
                conservatives_index.append(i)
        return conservatives_index

    
    def info(self):
    # prints basic information about legislature
        print 'Number of liberals = '+str(self.num_liberals)
        print 'Number of conservatives = '+str(self.num_conservatives)
        print 'Median ideology = '+str(median(self.ideologies))
        print 'Standard deviation of tolerances = '+str(std(self.tolerances))
        print ''
        
    def show_ideology_distribution(self,num_bins=10):
    # displays a histogram of ideological distribution in legislature
        P.figure()
        P.hist(self.ideologies,num_bins)
        P.title('Distrobution of ideologies')
        P.xlabel('Ideologies')
        P.ylabel('Number of agents')
        
    def show_tolerance_distribution(self,num_bins=10):
    # displays a histogram of tolerance distribution in legislature
        P.figure()
        h,r=P.histogram(self.tolerances,num_bins)
        P.bar(r,h,width=0.05)
        P.title('Distrobution of tolerances')
        P.xlabel('Tolerances')
        P.ylabel('Number of agents')

    def show_tolerance_scatter(self):
    # displays a scatter plot of ideologies (X) by tolerances (Y)
        P.figure()
        P.scatter(self.ideologies,self.tolerances)
        P.title('Real ideology X Tolerance')
        P.xlabel('Real ideology')
        P.ylabel('Tolerance')

    def show_utility_scatter(self):
    # displays a scatter plot of agent ideologies (X) by utility (Y)
        P.figure()
        ideologies=self.get_ideologies()
        utilities=self.get_utilities()
        P.scatter(ideologies,utilities)
        P.title('Real Ideologies X Utilities')
        P.xlabel('Real ideology')
        P.ylabel('Utilities')


class Committee:
# Returns a committee with a given size from a given TwoPartyLegislature class capable of producing a bill
    def __init__(self,legislature,size,members=None,sq=None,majority_rule=True):
        # Class variables provided outside the class
        self.committee_size=size    # Committe size is given by user
        self.id=int                 # Multiple committees

        # Class variables provided inside the class
        self.committee_index=[] 	# Index of committee memebers
        self.liberal_count=0    # Additional record keeping to provide majority rule
        self.conservative_count=0
        self.num_liberal_members=int    # Final records for committee balance
        self.num_conservative_members=int
        self.status_quo=float           # Variable used when creating a bill
        self.majority_ideologies=[]     # Collected to set status quo
        self.minority_ideologies=[]
        self.ideologies=[]              # Collected to create bill

        if members is not None:
        # This allows the users to predefine the committee memebers
            if len(members)!=size:
                Warning('Committee size and number of members do not match, using members provided')
            else:
                self.committee_index=members
                for i in self.committee_index:
                    if legislature.get_agent(i).get_party()<1:
                        self.liberal_count+=1
                    else:
                        self.conservative_count+=1
                self.num_liberal_members=self.liberal_count
                self.num_conservative_members=self.conservative_count
        else:
        # If the committee is set randomly...
        #
        # The legislature's party balance determines the number of agents from each party on the committee 
            if legislature.get_num_liberals()>legislature.get_num_conservatives():
            # If the legislature has a liberal majority, or
                if size % 2>0:
                # Tests if committee size is odd or even
                        self.num_liberal_members=(size/2)+1
                        self.num_conservative_members=(size/2)
                else:
                        self.num_liberal_members=(size/2)
                        self.num_conservative_members=(size/2)
            else:
            # if the legislature has a conservative majority
                if size % 2>0:
                        self.num_liberal_members=(size/2)
                        self.num_conservative_members=(size/2)+1
                else:
                        self.num_liberal_members=(size/2)
                        self.num_conservative_members=(size/2)

            # Now randonly select agents from the legislature w/o replacement
            while len(self.committee_index)<size:
                i=int(random.random_integers(0,legislature.size()-1)) # random agent
                if legislature.get_agent(i).get_party()<1:
                # If agent is a liberal, and
                    if self.liberal_count<self.num_liberal_members:
                    # we have not already reached our maxiumum number of liberals, and
                        if self.committee_index.count(i)<1:
                        # we have not already selected this agent, then add the agent to the committee and do
                        # appropriate record keeping
                            self.committee_index.append(i)
                            self.liberal_count=self.liberal_count+1
                else:
                # Analogous process for selecting conservative agent
                    if self.conservative_count<self.num_conservative_members:
                        if self.committee_index.count(i)<1:
                            self.committee_index.append(i)
                            self.conservative_count=self.conservative_count+1

        # Now collect the ideologies of everyone, and keep seperate the ideologies of the majority party 
        if self.num_liberal_members>self.num_conservative_members:
            for m in self.committee_index:
                current_agent=legislature.get_agent(m)
            # Record ideologies of majority liberal members, but all keep track of everyone's ideology.
            # The agenda setter will need to know these in order to propose appropriate legislation
                if current_agent.get_party()<1:
                    self.majority_ideologies.append(current_agent.get_ideology())
                    self.ideologies.append(current_agent.get_ideology())
                else:
                    self.minority_ideologies.append(current_agent.get_ideology())
                    self.ideologies.append(current_agent.get_ideology())
        else:
        # Analogous for conservative majority
            for m in self.committee_index:
                current_agent=legislature.get_agent(m)
                if current_agent.get_party()>0:
                    self.majority_ideologies.append(current_agent.get_ideology())
                    self.ideologies.append(current_agent.get_ideology())
                else:
                    self.minority_ideologies.append(current_agent.get_ideology())
                    self.ideologies.append(current_agent.get_ideology())
        # If using majority rule, set the status quo of the committee to the median member of the majority
        if sq is None:
            if majority_rule is False:
            # If no status quo is provided and not using majority rule, status_quo set by nature
                self.status_quo=median(self.ideologies)
            else:
            # If using majority rule, status_quo equal to median committee member of majority party
                self.status_quo=median(self.majority_ideologies)
        else:
        # Otherwise the user has provided a status quo value
            self.status_quo=sq     
    
    @classmethod
    def _committee_vote(cls,bill_ideology,legislature,members,sq):
	# Internal committee voting mechanism
        member_objects=[]
        YAYS=0
        NAYS=0
        for m in members:
        # Collect memeber agent objects
                member_objects.append(legislature.get_agent(m))
        for a in member_objects:
        # Ask each agent to vote on the bill at committee
                if a.vote(bill_ideology,sq,probabilistic=True):
                        YAYS+=1
                else:
                        NAYS+=1
        if YAYS>NAYS:
                return True
        else:
                return False

    # THIS FUNCTION MAY BE THE MAJOR CONTRIBUTION TO THE LITERATURE 
    # The bill creation process has two layers.  The agent must consider a first the make up of the committee, then of the
    # legislature when proposing bills, because there is no utility in proposing bills that will not pass.
    def make_bill(self,legislature,agenda_setter,timed=False,verbose=False,prob_voting=True):  
    # I don't like having to look back again to the legislature by passing it through the committee to this function, 
    # but this prevents having to make copies of the agents themselves in the committee.
        if self.committee_index.count(agenda_setter)<1:
        # Make sure agent ID being passed makes sense
            raise ValueError('Agenda setter must be the index of a committee member')
        else:
            # Get party and ideology of agenda setter, as well as the committee status quo
            agenda_setter_party=legislature.get_agent(agenda_setter).get_party()
            agenda_setter_ideology=legislature.get_agent(agenda_setter).get_ideology()
            sq=self.get_status_quo()
            ranked_ideologies=self.get_all_ideologies()
            ranked_ideologies.sort()
            current_ideological_rank=ranked_ideologies.index(agenda_setter_ideology)
            trials=0
            num_voters=len(self.committee_index)
            if agenda_setter_ideology==sq:
            # If the status quo matches the idelogy of the agenda setter, and there is no reason to
            # propose new legislation, as this will hurt the agenda setter's utility
                print 'Status quo is already defined by the agenda setter'
                B=None,trials
            # Meaningful bill making procudure
            else:
                B=Bill(sq=self.get_status_quo())
                B.set_party(agenda_setter_party)
                # Agenda setter has a different calculus when there is pressure to move legislation quickly
                # Can the agenda setter force the committee to change the SQ to its ideal point due to the rush to legislate?
                # To answer this, the AS must know the relative payoffs for each member on the committee to its ideal point and the current SQ,
                # in this case the AS may be able to force the committee to pass a bill they normally would have rejected.  
                while B.passed()!=True:
                    try:
                        B.set_ideology(ranked_ideologies[current_ideological_rank])
                        B.set_bill_num(trials)
                        if verbose:
                            B.info()
                        for i in self.committee_index:
                            a=legislature.get_agent(i)
                            if a.vote(B.get_ideology(),sq,trials,quick=timed,probabilistic=prob_voting,verbose=verbose):
                                B.add_record(a,1)
                            else:
                                B.add_record(a,0)
                    except IndexError:
                        print 'WARNING: Voters deadlocked at after '+str(trials)+' attempts'
                        B.set_ideology(None)
                        return B,trials
                    trials+=1
                    current_ideological_rank-=1
                return B,trials-1


    def get_size(self):
    # Returns size of committee 
        return self.committee_size

    def get_members(self):
    # Returns list of committee agent IDs
        return self.committee_index

    def get_num_liberals(self):
    # Return count of liberal members
        return self.num_liberal_members

    def get_num_conservatives(self):
    # Return count of conservative members
        return self.num_conservative_members

    def majority(self):
    # Returns 0 if committee is a liberal majority, and 1 for conservative
        if self.get_num_liberals()>self.get_num_conservatives():
            return 0
        else:
            return 1

    def get_status_quo(self):
    # Return the status quo value of this committee
        return self.status_quo

    def get_majority_ideologies(self):
    # Returns ideologies of committee majority members
        return self.majority_ideologies

    def get_minority_ideologies(self):
    # Returns ideologies of committee minority members
        return self.minority_ideologies

    def get_all_ideologies(self):
    # Returns ideologies of all committee members
        return self.ideologies

    def show_member_info(self,legislature):
    # Prints detailed information about committee members
        for i in self.get_members():
            legislature.get_agent(i).info()

    def info(self):
    # Returns more detailed information about committee members
        print '--- Committee Info ---'
        print 'Size: '+str(self.get_size())
        print 'Status quo: '+str(self.get_status_quo())
        if self.majority()<1:
            print '-- Liberal majority --'
            print 'Number of liberals: '+str(self.get_num_liberals())
            print 'Median ideologiy: '+str(median(self.get_majority_ideologies()))
            print ''
            print 'Number of conservatives: '+str(self.get_num_conservatives())
            print 'Median ideology: '+str(median(self.get_minority_ideologies()))
        else:
            print '-- Conservative majority --'
            print 'Number of conservatives: '+str(self.get_num_conservatives())
            print 'Median ideologiy: '+str(median(self.get_majority_ideologies()))
            print ''
            print 'Number of liberals: '+str(self.get_num_liberals())
            print 'Median ideology: '+str(median(self.get_minority_ideologies()))

class Bill:
    """The object that is voted on by agents"""

    def __init__(self,billid=None,party=None,ideology=None,sq=None):
        """Class variables provided outside the class"""
        self.party=party        # The party that introduces bill
        self.ideology=ideology  # 0.0 < liberal < 0.5 < conservative < 1.0
        self.status_quo=sq		# Previous status quo the bill seeks to change
        self.record={}          # Voting record, keys: agent object, values: binary
        self.id=billid          # Bill number
        
        # Class variables provided inside the class
        self.bill_pass=False    # True when bill is passed
    
    def get_party(self):
        """Return party that introduced bill"""
        return self.party
    
    def get_ideology(self):
        """Return bill ideology"""
        return self.ideology

    def get_status_quo(self):
        """Rerturn status quo bill seeks to change"""
        return self.status_quo
    
    def get_bill_num(self):
        """Return bill numbers"""
        return self.id
        
    def get_passage(self):
	    """Return True if bill passed"""
	    return self.bill_pass

    def get_record(self):
        """Return agent voting record of bill"""
        return self.record
    
    def set_bill_num(self,num):
        """Assign bill number"""
        self.id=num

    def set_party(self,party_input):
        """Assign party that introduced bill"""
        self.party=party_input
    
    def set_ideology(self,ideology_input):
        """Assign bill ideology"""
        self.ideology=ideology_input
    
    def set_passage(self,pass_bool):
        """Set True if bill passed"""
        self.bill_pass=pass_bool
        
    def add_record(self,agent,vote):
        """Add to bill voting record, keys: TwoPartyAgent, values: binary"""
        self.record[agent]=vote
        
    def passed(self):
	# Get voting results
        if len(self.record)<1:
                return False
        else:
            if sum((self.num_liberal_yays(),self.num_conservative_yays()))>len(self.record)/2:
            # If there are more YAYS than NAYS, the bill passed
                self.set_passage(True)
                return True
            else:
                self.set_passage(False)
                return False
	
    def num_liberal_yays(self):
        """return number of liberal yay votes"""
        liberal_yays=0
        record_items=self.record.items()
        yay_voters=[(a) for (a,b) in record_items if b>0]
        for a in yay_voters:
            if a.get_party()<1:
                liberal_yays+=1
        return liberal_yays
                
    def num_conservative_yays(self):
        """return number of conservative yay votes"""
        conservative_yays=0
        record_items=self.record.items()
        yay_voters=[(a) for (a,b) in record_items if b>0]
        for a in yay_voters:
            if a.get_party()>0:
                conservative_yays+=1
        return conservative_yays
    
    def info(self):
        """Returns key bill information"""
        print 'Number: '+str(self.id)
        if self.get_party()<1:
            print 'Introduced by liberal'
        else:
            print 'Party that introduced: Conservative'
        print 'Ideological score: '+str(self.ideology)
        if len(self.record)<1:
            print 'Bill has not yet been voted on'
        else:
            lib_yays=self.num_liberal_yays()
            cons_yays=self.num_conservative_yays()
            if self.bill_pass:
                print 'Bill passed with '+str(lib_yays+cons_yays)+' yay votes and '+str(len(self.record)-(lib_yays+cons_yays))+' nay votes'
            else:
                print 'Bill did not pass with '+str(lib_yays+cons_yays)+' yay votes'+str(len(self.record)-(lib_yays+cons_yays))+' nay votes'
            print '--- Vote Breakout ---'
            print 'Liberal yay votes: '+str(lib_yays)
            print 'Conservative yay votes: '+str(cons_yays)


class TwoPartyAgent(object):
    """Agent class taking either a 'liberal' or 'conservative' party and ideology"""

    LIBERAL_PARTY=0
    CONSVRT_PARTY=1 
    PARTIES=(LIBERAL_PARTY,CONSVRT_PARTY)

    def __init__(self,ideology_input=None,tolerance_function=None):
        """Class variables given outside the class init"""
        self.id=int
        self.record={}  # Voting record, keys: bill num, values: binary
        self.utility=0.0
        
        # Default tolerance calculated as a form of the normal distribution PDF,
        # this is an area that needs to be fleshed out in much greater detail
        # LaTex of the function: $\frac{1}{1.4\sqrt{2\pi}}e^{\frac{-(t-0.5)^{2}}{(0.5)^{2}}}$
        self.tolerance=float    # Assigned via the lambda function below
        if tolerance_function is None:
            tolerance_function = lambda t: (1.0/(1.4*sqrt(2.0*pi)))*e**(-1.0*(t-0.5)**2.0/(0.5)**2)
        
        if ideology_input is None:
            #Generate random ideology value if not specified by caller
            ideology_input=self._generate_ideology()

        if ideology_input <0.0 or ideology_input>1.0:
        # Value must be between 0.0 and 1.0
            raise ValueError('Agent ideology(%s) must be 0.0 < liberal < 0.5 < conservative < 1.0'%ideology_input)
        else:
            self._party=self._determine_party(ideology_input)
            self.ideology=ideology_input
            if self.ideology > .5:
                self.tolerance=self.ideology-tolerance_function(self.ideology)
            else:
                self.tolerance=self.ideology+tolerance_function(self.ideology)

    @staticmethod
    def _generate_ideology():
        """generate random ideology value"""
        idelogy_vector=random.sample(100)
        random.shuffle(idelogy_vector)
        return idelogy_vector[0]

    @classmethod
    def _determine_party(cls,ideology_input):
        """Return the party of an agent based on their ideology"""
        # In this construction, if an agent is given an ideolgy of exactly 0.5, it is liberal,
        # this could also be determined by a coin toss
        if ideology_input<=0.5: 
            return cls.LIBERAL_PARTY
        else:
            return cls.CONSVRT_PARTY
	
    @classmethod
    def _probability_execution(cls,P):
	# Returns True with probability P by selecting values from a uniform distribution [0.0,1.0]
        if random.uniform() <= P:
                return True
        else:
                return False

    @classmethod
    def _cumulative_loss(cls,agent_ideology,status_quo,period,speed=False):
        if period<1:
            U0=cls._calculate_utility(agent_ideology,status_quo,period,fast=speed)
            U1=cls._calculate_utility(agent_ideology,status_quo,period+1,fast=speed)
            loss = cls._distance(U1,U0)
            return loss
        else:
            losses=[]
            for t in range(0,period):
                Ut=cls._calculate_utility(agent_ideology,status_quo,t,fast=speed)
                Ut_1=cls._calculate_utility(agent_ideology,status_quo,t+1,fast=speed)
                loss_at_t=cls._distance(Ut_1,Ut)
                losses.append(loss_at_t)
##                print 'Current cumulative losses: '+str(losses)
            return sum(losses)

    @classmethod
    def _calculate_utility(cls,agent_ideology,bill_ideology,period,fast=False):
        if fast:
            if period<1:
                U=lambda x,y,t: (1-cls._distance(x,y))-t
            else:
                U=lambda x,y,t: (1-cls._distance(x,y))-(float(t)/(pi*sqrt(t)))	# utility when time is short
        else:
            if period<1:
                U=lambda x,y,t: (1-cls._distance(x,y))-t
            else:
                U=lambda x,y,t: (1-cls._distance(x,y))-(float(t)/((pi**6)*sqrt(t)))  # utility when time is long
        return U(agent_ideology,bill_ideology,period)
		
    @classmethod
    def _distance(cls,x,y):
        return abs(x-y)
    
    def vote(self,bill_ideology,status_quo,period,quick=False,probabilistic=True,verbose=False):
	# Agent's internal voting mechanism.
        my_id=self.get_id()
        my_ideology=self.get_ideology()
        distance_from_sq=self._distance(my_ideology,status_quo)
        distance_from_new=self._distance(my_ideology,bill_ideology)
        sq_utility_1=self._calculate_utility(my_ideology,status_quo,period,fast=quick)
        sq_utility_2=self._calculate_utility(my_ideology,status_quo,period+1,fast=quick)
        new_utility_1=self._calculate_utility(my_ideology,bill_ideology,period,fast=quick)
        new_utility_2=self._calculate_utility(my_ideology,bill_ideology,period+1,fast=quick)
        if verbose:
            print 'my_id: '+str(my_id)
            print 'my_ideology: '+str(my_ideology)
            print 'distance_from_sq: '+str(distance_from_sq)
            print 'distance_from_new: '+str(distance_from_new)
            print 'sq_utility_1: '+str(sq_utility_1)
            print 'sq_utility_2: '+str(sq_utility_2)
            print 'new_utility_1: '+str(new_utility_1)
            print 'new_utility_2: '+str(new_utility_2)
        if probabilistic:
            if sq_utility_2<0.0:
            # If agent is going to experience negative utlity in this or the next period, it is forced to vote YAY 
                #print "sq_utility_2: "+str(sq_utility_2)+" so we set prob=1.0"
                vote=self._probability_execution(1.0)
            else:
            # Otherwise we enter into the decision tree
                sq_loss=self._distance(sq_utility_2,sq_utility_1)       # Loss in utility for sq position between t and t+1
                new_loss=self._distance(new_utility_2,new_utility_1)    # Loss in utility for new position between t and t+1
                if new_utility_1>=sq_utility_1:
                # Agent is inclined to vote for bill because new position provides more utility than sq.
                # The probability is then the utility the agent will gain from the new policy, plus 
                # the loss from not getting the bill passed in this round to further increase the
                # probability this agent votes for the bill.
                    prob=new_utility_1+new_loss 
                    if prob>1.0:
                    # If the loss is great enough, it can force the agent to vote for the bill
                        prob=1.0
                    if verbose:
                        print 'I WANT TO VOTE FOR THIS'
                        print 'new_utility_1: '+str(new_utility_1)+' >=sq_utility_1: '+str(sq_utility_1)
                        print 'new_loss: '+str(new_loss)
                        print 'prob = '+str(prob)
                    vote=self._probability_execution(prob)
                else:
                # When the agent loses utility from the new policy position the procedure is slightly more nuanced.
                # A rational actor would simply never vote for this change, because it does hurts decreases their
                # utility to the status quo.  To add some stochastic noise, we add the loss incurred by waiting for
                # the status quo to pass in the sum of loss based on how long the agent has held out.
                    prob=self._cumulative_loss(my_ideology,status_quo,period,speed=quick)
                    if prob>1.0:
                        prob=1.0
                    if verbose:
                        print 'I DO NOT WANT TO VOTE FOR THIS'
                        print 'new_utility_1: '+str(new_utility_1)+' <sq_utility_1: '+str(sq_utility_1)
                        print 'sq_loss: '+str(sq_loss)
                        print 'prob = '+str(prob)
                    vote=self._probability_execution(prob)
            if verbose:
                if vote:
                    print 'Vote: YAY'
                else:
                    print 'Vote: NAY'
                print ''
            return vote
        else:
        # Agent is strictly utility maximizing
            if sq_utility_1<new_utility_1:
                return True
            else:
                return False

    def get_record(self):
        """returns agent voting record"""
        return self.record
        
    def get_ideology(self):
        """returns agent ideology"""
        return self.ideology
        
    def get_tolerance(self):
        """returns agent adjusted tolerance"""
        return self.tolerance
        
    def get_utility(self):
        """returns agent utility score"""
        return self.utility
        
    def get_id(self):
        """returns agent ID"""
        return self.id
        
    def get_party(self):
        """returns agent party (0=liberal, 1=conservative)"""
        return self._party
    
    def set_id(self, agent_id):
        """Assign agent ID"""
        if not isinstance(agent_id,int):
            raise ValueError("id's must be ints not %s"%str(type(agent_id)))
        self.id=agent_id
        
    def set_utility(self, new_utility):
        """Assign agent a new utility"""
        self.utility=self.utility-new_utility
    
    def add_record(self, bill_num,vote):
        """Add to agent voting record"""
        if vote not in (0,1):
            raise ValueError("Agent vote must be 0 or 1")
        else:
            self.record[bill_num]=vote
            
    def info(self,print_to=sys.stdout):
        """Prints key agent information"""
        print >>print_to,'ID: '+str(self.id)
        if self._party==self.LIBERAL_PARTY:
            print >>print_to,'Member of liberal party'
        else:
            print >>print_to,'Member of conservative party'
        print >>print_to,'Ideology: '+str(self.ideology)

    def equals(self,other):
        """Compare two agents to see if they're equal"""
        return self.get_id()==other.get_id()

    def __eq__(self,other):
        return self.equals(other)

    def __ne__(self,other):
        return not self.equals(other)

class TwoPartyAgentTests(unittest.TestCase):

    agent=None


    def get_agent(self,ideology_input=None,tolerance_function=None):
        """Assemble a TwoPartyAgent with specified constructor arguments"""
        #Override so that party is always the same for testing
        f,random.randint=random.randint,lambda x,y:0
        agent=TwoPartyAgent(ideology_input,tolerance_function)
        random.randint=f
        return agent

    def setUp(self):
        self.agent=self.get_agent(.1)

    def test_init_ideology_out_of_range(self):
        """ValueError should be raised when ideology is out of range"""
        self.assertRaises(ValueError,TwoPartyAgent,-.1)
        self.assertRaises(ValueError,TwoPartyAgent,1.1)

    def test_init_ideology_sets_party(self):
        """Passing in an ideology should set the appropriate party affiliation"""
        agent=self.get_agent(.1)
        self.assertEquals(0,agent.get_party())
        agent=self.get_agent(.9)
        self.assertEquals(1,agent.get_party())

    def test_init_tolerance_from_tolerancefunc_and_ideology(self):
        """A passed in tolerance function should be used to generate the tolerance level"""
        f=lambda x:x+1
        agent=self.get_agent(0.1,f)
        self.assertEquals(.1+f(.1),agent.get_tolerance())

    def test_init_tolerance_from_ideology(self):
        """Ideology specified should result in specific tolerance"""
        agent=self.get_agent(0.1)
        self.assertEquals(0.25025660149009504,agent.get_tolerance())

    def test_init_tolerance_for_consv(self):
        """Check tolerance for conservative agent"""
        f=lambda x:x+1
        ideol=0.9
        agent=self.get_agent(ideol,f)
        self.assertEquals(ideol-f(ideol),agent.get_tolerance())
        
    def test_get_party(self):
        """Party should equal zero due to party def override"""
        party=self.agent.get_party()
        self.assertEquals(TwoPartyAgent.LIBERAL_PARTY,party)


    def test_get_record(self):
        """Voting record should be a dictionary"""
        record=self.agent.get_record()
        self.assert_(isinstance(record,dict))


    def test_set_id(self):
        """set_id() should modify result of get_id()"""
        self.agent.set_id(3)
        self.assertEquals(3,self.agent.get_id())

    def test_set_id_invalid_type(self):
        """set_id() should raise error if id is not int"""
        self.assertRaises(ValueError,self.agent.set_id,'foo')

    def test_add_record_invalid_vote(self):
        """Invalid vote should raise an error"""
        self.assertRaises(ValueError,self.agent.add_record,1,1.1)
        self.assertRaises(ValueError,self.agent.add_record,1,-.1)
        self.assertRaises(ValueError,self.agent.add_record,1,.5)

    def test_add_record(self):
        """Added records should be returned by get record"""
        self.agent.add_record(1,0)
        self.agent.add_record(2,1)
        self.agent.add_record(3,1)
        votedict={1:0,2:1,3:1}
        self.assertEquals(votedict,self.agent.get_record())

    def test_info_appropriate_party(self):
        """info() should display the appropriate party"""
        #Redirect print statements to StringIO object
        import StringIO
        buff=StringIO.StringIO()
        self.agent.info(buff)
        self.assertEquals('Member of liberal party',buff.getvalue().split('\n')[1])

    def test_determine_party(self):
        """determine_party() should return appropriate party id based on ideology"""
        self.assertEquals(self.agent.LIBERAL_PARTY,self.agent._determine_party(.2))
        self.assertEquals(self.agent.CONSVRT_PARTY,self.agent._determine_party(.7))
        self.assertEquals(self.agent.LIBERAL_PARTY,self.agent._determine_party(.5))
            
    def test_equality_istrue(self):
        """Check overrided equality function when agents equal"""
        agent1=TwoPartyAgent()
        agent2=TwoPartyAgent()
        agent1.set_id(1)
        agent2.set_id(1)
        self.assertTrue(agent1==agent2)
        self.assertTrue(agent2==agent1)

    def test_equality_isfalse(self):
        """Check overrided equality function when agents not equal"""
        agent1=TwoPartyAgent()
        agent2=TwoPartyAgent()
        agent1.set_id(1)
        agent2.set_id(2)
        self.assertFalse(agent1==agent2)
        self.assertFalse(agent2==agent1)

    def test_notequals_istrue(self):
        """Check overrided inequality function when agents not equal"""
        agent1=TwoPartyAgent()
        agent2=TwoPartyAgent()
        agent1.set_id(1)
        agent2.set_id(2)
        self.assertTrue(agent1!=agent2)
        self.assertTrue(agent2!=agent1)

    def test_notequals_isfalse(self):
        """Check overrided inequality function when agents not equal"""
        agent1=TwoPartyAgent()
        agent2=TwoPartyAgent()
        agent1.set_id(1)
        agent2.set_id(1)
        self.assertFalse(agent1!=agent2)
        self.assertFalse(agent2!=agent1)


class TestBill(unittest.TestCase):

    bill=None
    billid=3
    ideology=.4
    party=TwoPartyAgent.CONSVRT_PARTY

    def setUp(self):
        self.bill=Bill(self.billid,self.party,self.ideology)
        self.bill.add_record(TwoPartyAgent(.7),True)
        self.bill.add_record(TwoPartyAgent(.8),False)
        self.bill.add_record(TwoPartyAgent(.2),False)
        self.bill.add_record(TwoPartyAgent(.3),True)

    def test_get_party(self):
        """get_party() should return the previously specified party"""
        self.assertEquals(self.party,self.bill.get_party())

    def test_get_ideology(self):
        """get_ideology() should return the previously specified ideology"""
        self.assertEquals(self.ideology,self.bill.get_ideology())

    def test_get_bill_num(self):
        """get_bill_num() should return the previously specified bill number"""
        self.assertEquals(self.billid, self.bill.get_bill_num())

    def test_get_record_datatypes(self):
        """get_record() should return dictionary of agent votes"""
        voterec=self.bill.get_record()
        self.assertTrue(isinstance(voterec,dict))
        self.assertNotEquals(0,len(voterec))
        for agnt in voterec:
            vote=voterec[agnt]
            self.assertTrue(isinstance(agnt,TwoPartyAgent))
            self.assertTrue(vote in (0,1))

    def test_set_bill_num(self):
        """set_bill_num() should set the bill number"""
        self.bill.set_bill_num(3)
        self.assertEquals(3, self.bill.get_bill_num())

    def test_set_party(self):
        """set_party() should set the bill's party"""
        self.bill.set_party(1)
        self.assertEquals(1,self.bill.get_party())

    def test_set_ideology(self):
        """set_ideology() should set the bill's ideology"""
        self.bill.set_ideology(.3)
        self.assertEquals(.3,self.bill.get_ideology())

    def test_set_passage(self):
        """set_passage() if the bill passed"""
        self.bill.set_passage(True)
        self.assertTrue(self.bill.bill_pass)

    def test_add_record(self):
        """add_record() ads a vote for a particular agent"""
        agnt=TwoPartyAgent()
        self.bill.add_record(agnt,True)
        self.assertTrue(self.bill.record[agnt])
        
    def test_num_lib_yays(self):
        """num_liberal_yays() should return number of liberal party agents voting yes"""
        self.assertEquals(1,self.bill.num_liberal_yays())

    def test_num_consv_yays(self):
        """num_conservative_yays() should return number of conservative party agents voting yes"""
        self.assertEquals(1,self.bill.num_conservative_yays())

    def test_info_no_votes(self):
        """info() should handle bill that hasn't been voted on"""
        import StringIO
        buff=StringIO.StringIO()
        sys.stdout=buff
        self.bill.info()
        sys.stdout=sys.__stdout__

    def test_info_with_votes(self):
        """info() should handle bill that has been voted on"""
        agnt=TwoPartyAgent()
        self.bill.add_record(agnt,True)
        import StringIO
        buff=StringIO.StringIO()
        sys.stdout=buff
        self.bill.info()
        sys.stdout=sys.__stdout__


class TestTwoPartyLegislature(unittest.TestCase):
    """Test case for the L{TwoPartyLegislature} class"""
    
    legislature=None
    legis_size=100

    def setUp(self):
        self.legislature=TwoPartyLegislature(self.legis_size)

    def test_init_expected_sizes(self):
        """Specified legislature size should result in that number of agents"""
        self.assertEquals(self.legis_size,self.legislature.size())
        self.assertEquals(self.legis_size,len(self.legislature.get_utilities()))
        self.assertEquals(self.legis_size,len(self.legislature.get_ideologies()))
        self.assertEquals(self.legis_size,len(self.legislature.legislators()))
        party_sum=self.legislature.get_num_liberals()+self.legislature.get_num_conservatives()
        self.assertEquals(self.legis_size,party_sum)

    def test_conservative_count(self):
        """num_conservatives() should equal len() of find_conservatives()"""
        self.assertEquals(self.legislature.get_num_conservatives(), len(self.legislature.find_conservatives()))
        
    def test_liberal_count(self):
        """num_liberals() should equal len() of find_liberals()"""
        self.assertEquals(self.legislature.get_num_liberals(),len(self.legislature.find_liberals()))

    def test_majority_value(self):
        """majority() should return a valid party number"""
        self.assertTrue(self.legislature.majority() in (0,1))

        



if __name__ == '__main__':
    unittest.main()

